/*
 * Copyright (c) 2018, apexes.net. All rights reserved.
 *
 *         http://www.apexes.net
 *
 */
package net.apexes.commons.lang;

import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.util.BitSet;
import java.util.Random;
import java.util.concurrent.atomic.AtomicLong;

/**
 *
 * @author <a href=mailto:hedyn@foxmail.com>HeDYn</a>
 */
public class IDGenerator {

    private final byte[] idedBytes;

    private IDGenerator(byte[] idedBytes) {
        this.idedBytes = idedBytes;
    }

    public String gen() {
        return IDGenerator.genId(idedBytes);
    }

    private static final AtomicLong LAST_TIME = new AtomicLong(Long.MIN_VALUE);
    private static final Ided IDED = new Ided();

    static String genId() {
        return genId(IDED.systemIdedBytes());
    }

    static String genId(byte[] idedBytes) {
        ByteBuffer buf = ByteBuffer.allocate(16);
        buf.putLong(timeMillis());
        buf.put(idedBytes);
        return IDs.toBase58Id(buf.array());
    }

    private static long timeMillis() {
        long timeMillis = System.currentTimeMillis();
        // 每毫秒最多8192个
        timeMillis <<= 13;
        while (true) {
            long current = LAST_TIME.get();
            if (timeMillis > current) {
                if (LAST_TIME.compareAndSet(current, timeMillis)) {
                    break;
                }
            } else {
                if (LAST_TIME.compareAndSet(current, current + 1)) {
                    timeMillis = current + 1;
                    break;
                }
            }
        }
        return timeMillis;
    }

    static IDGenerator namespace(long namespace) {
        return new IDGenerator(Bytes.longToBytes(namespace));
    }

    static IDGenerator namespace(int major, short minor) {
        byte[] majorBytes = Bytes.intToBytes(major);
        byte[] minorBytes = Bytes.shortToBytes(minor);
        byte[] pidBytes = IDED.pidBytes();
        byte[] namespaceBytes = new byte[8];
        namespaceBytes[0] = pidBytes[0];
        namespaceBytes[1] = pidBytes[1];
        namespaceBytes[2] = minorBytes[0];
        namespaceBytes[3] = minorBytes[1];
        namespaceBytes[4] = majorBytes[0];
        namespaceBytes[5] = majorBytes[1];
        namespaceBytes[6] = majorBytes[2];
        namespaceBytes[7] = majorBytes[3];
        return new IDGenerator(namespaceBytes);
    }

    private static class Ided {

        private volatile byte[] pidBytes;
        private volatile byte[] systemIdedBytes;

        private Ided() {
        }

        byte[] pidBytes() {
            if (pidBytes == null) {
                synchronized (Ided.class) {
                    if (pidBytes == null) {
                        init();
                    }
                }
            }
            return pidBytes;
        }

        byte[] systemIdedBytes() {
            if (systemIdedBytes == null) {
                synchronized (Ided.class) {
                    if (systemIdedBytes == null) {
                        init();
                    }
                }
            }
            return systemIdedBytes;
        }

        private void init() {
            short pid = ProcessIdentifier.Impl.processId();
            Long macValue = Networks.macValue();
            if (macValue == null) {
                try {
                    byte[] localBytes = InetAddress.getLocalHost().getAddress();
                    int localValue = Bytes.bytesToInt(localBytes);
                    macValue = (long) localValue;
                    macValue <<= 8;
                } catch (Exception e) {
                    Random random = new Random(System.currentTimeMillis());
                    macValue = random.nextLong();
                }
            }
            BitSet bitSet = new BitSet(64);
            long m = 1;
            for (int i = 0; i < 16; i++) {
                if ((pid & m) != 0) {
                    bitSet.set(i * 4);
                }
                m <<= 1;
            }
            m = 1;
            for (int i =0; i < 48; i++) {
                if ((macValue & m) != 0) {
                    bitSet.set(i + (i / 3) + 1);
                }
                m <<= 1;
            }
            systemIdedBytes = bitSet.toByteArray();
            pidBytes = Bytes.shortToBytes(pid);
        }

    }

}
